package main

import (
	"fmt"
	"os"   // 操作系统接口，用于文件操作
	"time" // 时间处理包，用于日志时间格式化和日志分割

	"go.uber.org/zap"         // zap主包，提供高性能结构化日志库
	"go.uber.org/zap/buffer"  // zap的缓冲区实现，用于日志数据的临时存储
	"go.uber.org/zap/zapcore" // zap的核心包，包含底层日志处理组件
)

// 程序功能说明：
// 本文件演示了如何整合 zap 日志库的多种高级功能：
// 1. 带颜色的控制台日志输出
// 2. 自定义日志前缀
// 3. 按日期进行日志文件分割
// 4. 错误日志单独存储
// 5. 全局日志器配置

// 定义ANSI颜色常量，用于控制台彩色输出
// ANSI转义序列可以在支持的终端中显示彩色文本
const (
	RedColor    = "\033[31m" // 红色 - 用于错误级别日志
	GreenColor  = "\033[32m" // 绿色 - 用于信息级别日志
	YellowColor = "\033[33m" // 黄色 - 用于警告级别日志
	BlueColor   = "\033[34m" // 蓝色 - 用于调试级别日志
	ResetColor  = "\033[0m"  // 重置颜色 - 恢复默认终端颜色
)

// myEncodeLevel 自定义日志级别颜色编码器
// 参数说明：
// - level: 当前日志的级别
// - enc: 编码器，用于将日志级别以特定格式添加到输出中
// 功能：根据不同的日志级别，为控制台输出添加不同的颜色标识
func myEncodeLevel(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	switch level {
	case zapcore.DebugLevel: // 调试级别-蓝色
		enc.AppendString(BlueColor + "DEBUG" + ResetColor)
	case zapcore.InfoLevel: // 信息级别-绿色
		enc.AppendString(GreenColor + "INFO" + ResetColor)
	case zapcore.WarnLevel: // 警告级别-黄色
		enc.AppendString(YellowColor + "WARN" + ResetColor)
	// 将所有严重级别（错误、宕机、致命错误）都用红色标识
	case zapcore.ErrorLevel, zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
		enc.AppendString(RedColor + "ERROR" + ResetColor)
	default: // 其他级别使用默认格式
		enc.AppendString(level.String())
	}
}

// logEncoder 自定义日志编码器结构体
// 功能：扩展 zapcore.Encoder 接口，实现自定义日志处理逻辑
// 字段说明：
// - zapcore.Encoder: 内嵌原始编码器，保留原有功能
// - errFile: 错误日志文件指针，用于单独存储错误级别日志
// - currentDate: 当前日期字符串，用于按日期分割日志
// - file: 普通日志文件指针，用于存储所有级别的日志
type logEncoder struct {
	zapcore.Encoder // 内嵌 zap 的编码器接口
	errFile         *os.File
	currentDate     string   // 记录当前日志文件对应的日期（如 "20231025"）
	file            *os.File // 当前正在写入的日志文件指针
}

// EncodeEntry 自定义日志条目编码方法
// 参数说明：
// - entry: 日志条目，包含时间戳、级别、消息等基本信息
// - fields: 额外的字段信息，用于结构化日志
// 返回值：
// - *buffer.Buffer: 包含编码后日志数据的缓冲区
// - error: 编码过程中的错误信息
// 功能：重写编码器的 EncodeEntry 方法，实现自定义日志处理
func (e logEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
	// 1. 先调用原始编码器进行基础编码（时间、级别、消息等格式化）
	buff, err := e.Encoder.EncodeEntry(entry, fields)
	if err != nil {
		return buff, err // 如果编码出错，直接返回错误
	}

	// 2. 添加自定义前缀 [myApp]
	// 从缓冲区中获取当前已格式化的日志内容
	data := buff.String()
	// 清空缓冲区，准备写入新内容
	buff.Reset()
	// 在原始日志内容前添加自定义应用标识前缀，并写回缓冲区
	buff.AppendString("[myApp] " + data)
	// 从缓冲区中重新读取添加前缀后的完整日志内容
	data = buff.String()

	// 3. 按日期进行日志分割
	// 获取当前日期（格式：年-月-日）
	now := time.Now().Format("2006-01-02")

	// 检查日期是否变更（跨天）
	if e.currentDate != now {
		// 创建新日期的日志目录，os.MkdirAll会自动创建不存在的父目录
		os.MkdirAll(fmt.Sprintf("logs/%s", now), 0666) // 0666表示文件权限：所有者、组用户、其他用户都可读写

		// 构建普通日志文件路径
		name := fmt.Sprintf("logs/%s/out.log", now)
		// 打开或创建日志文件：
		// os.O_CREATE - 文件不存在时创建
		// os.O_APPEND - 追加模式，写入内容添加到文件末尾
		// os.O_WRONLY - 只写模式
		file, _ := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		e.file = file       // 更新文件指针
		e.currentDate = now // 更新当前日期
		// 注意：这里可以添加关闭旧文件的逻辑，以防止文件句柄泄漏
	}

	// 4. 根据日志级别进行不同处理
	switch entry.Level {
	case zapcore.ErrorLevel: // 如果是错误级别（ErrorLevel）的日志
		// 延迟初始化错误日志文件
		if e.errFile == nil {
			// 构建错误日志文件路径
			name := fmt.Sprintf("logs/%s/err.log", now)
			// 打开或创建错误日志文件
			file, _ := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
			e.errFile = file
		}
		// 将错误日志写入专门的错误日志文件
		e.errFile.WriteString(buff.String())
	}

	// 5. 将所有日志写入普通日志文件（前提是日期匹配，避免写入过期日志文件）
	if e.currentDate == now {
		e.file.WriteString(data)
	}

	// 返回编码后的缓冲区供控制台输出使用
	return buff, nil
}

// InitLog 初始化日志系统
// 返回值：配置好的 zap.Logger 实例
// 功能：配置并创建自定义的 zap 日志记录器，包含彩色输出、日志分割等高级功能
func InitLog() *zap.Logger {
	// 1. 使用 zap 的开发环境配置作为基础配置
	// NewDevelopmentConfig() 提供了适合开发环境的默认配置
	cfg := zap.NewDevelopmentConfig()

	// 2. 自定义时间格式为：年-月-日 时:分:秒
	cfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")

	// 3. 设置自定义的日志级别编码器，实现彩色输出
	cfg.EncoderConfig.EncodeLevel = myEncodeLevel

	// 4. 创建自定义的 Encoder 实例
	// 将 ConsoleEncoder 包装在自定义的 logEncoder 中，扩展其功能
	encoder := &logEncoder{
		Encoder: zapcore.NewConsoleEncoder(cfg.EncoderConfig), // 使用 Console 编码器作为基础
	}

	// 5. 创建 zap 日志核心组件
	// 参数说明：
	// - encoder: 使用自定义的编码器
	// - zapcore.AddSync(os.Stdout): 将日志同步输出到标准输出（控制台）
	// - zapcore.DebugLevel: 设置最低日志级别为 Debug，即所有级别日志都会被记录
	core := zapcore.NewCore(
		encoder,                    // 使用自定义的 Encoder
		zapcore.AddSync(os.Stdout), // 输出到控制台
		zapcore.DebugLevel,         // 设置日志级别为 Debug
	)

	// 6. 创建 Logger 实例
	// zap.AddCaller() 选项会在日志中添加调用者信息（文件名和行号）
	logger := zap.New(core, zap.AddCaller())

	// 7. 设置全局日志记录器
	// zap.ReplaceGlobals(logger) 将我们的自定义 logger 设置为 zap 的全局默认日志记录器
	// 之后可以通过 zap.L() 获取全局 logger，通过 zap.S() 获取全局 sugar logger
	zap.ReplaceGlobals(logger)

	return logger // 返回配置好的 logger 实例
}

// main 程序入口函数
func main() {
	// 初始化日志系统
	logger := InitLog()

	// 记录不同级别的日志示例
	logger.Debug("this is prod debug log") // 调试信息（蓝色，生产环境通常不显示）
	logger.Info("this is prod info log")   // 普通信息（绿色）
	logger.Warn("this is prod warn log")   // 警告信息（黄色）
	logger.Error("this is prod error log") // 错误信息（红色，同时会写入 err.log）

	// 使用全局 Sugar 日志器（提供更简洁的 API 和格式化功能）
	// Sugar 日志器支持类似 fmt.Sprintf 的格式化方式
	zap.S().Infof("%s xxx", "fengfeng") // 输出: [myApp] INFO 2023-xx-xx xx:xx:xx ... fengfeng xxx

	// 注意：在实际应用中，应该在程序结束前调用 logger.Sync() 确保所有日志都被写入文件
}

// 补充说明：
// 1. 本程序会在 logs/当前日期/ 目录下生成两个日志文件：
//    - out.log: 包含所有级别的日志
//    - err.log: 只包含错误级别的日志
// 2. 代码中的一些改进空间：
//    - 添加文件句柄关闭逻辑，防止资源泄漏
//    - 增加错误处理，避免使用 _ 忽略错误
//    - 可以配置日志文件大小限制和轮转策略
